package parser

import (
  "asteval"
  "astexec"
  "bufio"
  "scanner"
//  "errors"
  "fmt"
)

type Parser struct {
  look   byte
  lexeme string
  scan   *scanner.Scanner
}

type SyntaxError struct {
  message string
  eline int
}

func (e *SyntaxError) Error() string {
  return fmt.Sprintf("ERROR [%d] %s", e.eline, e.message)
}

func (p *Parser) Parse(src *bufio.Reader) astexec.Statement {
  p.look = scanner.None
  p.scan = scanner.New(src)
  p.match(scanner.None)
  ast, _ := p.sequence()
  p.match(scanner.Period)
  return ast
}

func (p *Parser) match(toks ...byte) error {
  for _, t := range toks {
    if p.look == t {
      p.lexeme, p.look = p.scan.Next()
      return nil
    }
  }
  return &SyntaxError{"Syntax error.", p.scan.Line()}
}

func (p *Parser) factor() (*asteval.Expression, error) {
  // number
  if p.look == scanner.Number {
    num := p.lexeme
    err := p.match(scanner.Number)
    if err != nil {
      return nil, err
    }
    return asteval.Number(num), nil
  }

  // variable
  if p.look == scanner.Ident {
    nam := p.lexeme
    p.match(scanner.Ident)
    return asteval.Variable(nam), nil
  }

  // unary operation '-'
  if p.look == scanner.Sub {
    p.match(scanner.Sub)
    ex, _ := p.factor()
    return asteval.Unary("-", ex), nil
  }

  // expression in parentheses
  if p.look == scanner.LPar {
    p.match(scanner.LPar)
    ex, _ := p.expr()
    p.match(scanner.RPar)
    return ex, nil
  }

  return nil, nil
}

func (p *Parser) term() (*asteval.Expression, error) {
  ex1, _ := p.factor()
  for p.look == scanner.Mul || p.look == scanner.Div {
    op := p.lexeme
    p.match(p.look)
    ex2, _ := p.factor()
    ex1 = asteval.Binary(op, ex1, ex2)
  }
  return ex1, nil
}

func (p *Parser) expr() (*asteval.Expression, error) {
  ex1, _ := p.term()
  for p.look == scanner.Add || p.look == scanner.Sub {
    op := p.lexeme
    p.match(p.look)
    ex2, _ := p.term()
    ex1 = asteval.Binary(op, ex1, ex2)
  }
  return ex1, nil
}

func (p *Parser) relation() (*asteval.Expression, error) {
  ex, _ := p.expr()
  if p.look >= scanner.Equal && p.look <= scanner.LsEq {
    op := p.lexeme
    p.match(p.look) // ?
    exi, _ := p.expr()
    ex = asteval.Binary(op, ex, exi)
  }
  return ex, nil
}

func (p *Parser) sequence() (astexec.Statement, error) {
  seq := astexec.NewSequence()
  st, _ := p.statement()
  seq.Append(st)
  for p.look == scanner.Semic {
    p.match(scanner.Semic)
    sti, _ := p.statement()
    seq.Append(sti)
  }
  return seq, nil
}

func (p *Parser) statement() (astexec.Statement, error) {
  var res astexec.Statement = nil
  switch p.look {
    case scanner.Ident:
      res, _ = p.assignment()
    case scanner.Print:
      res, _ = p.printexpr()
    case scanner.Input:
      res, _ = p.inputval()
    case scanner.If:
      res, _ = p.branching()
    case scanner.While:
      res, _ = p.looping()
  }
  return res, nil
}

func (p *Parser) assignment() (astexec.Statement, error) {
  nm := p.lexeme
  p.match(scanner.Ident)
  p.match(scanner.Equal)
  ex, _ := p.expr()
  return astexec.NewAssignment(nm, ex), nil
}

func (p *Parser) printexpr() (astexec.Statement, error) {
  p.match(scanner.Print)
  ex, _ := p.expr()
  return astexec.NewPrint(ex), nil
}

func (p *Parser) inputval() (astexec.Statement, error) {
  p.match(scanner.Input)
  nm := p.lexeme
  p.match(scanner.Ident)
  return astexec.NewInput(nm), nil
}

func (p *Parser) branching() (astexec.Statement, error) {
  p.match(scanner.If)
  ex, _ := p.relation()
  p.match(scanner.Then)
  st, _ := p.sequence()
  res := astexec.NewBranching(ex, st)

  t := res
  for p.look == scanner.ElseIf {
    p.match(scanner.ElseIf)
    ex, _ = p.relation()
    p.match(scanner.Then)
    st, _ = p.sequence()
    v := astexec.NewBranching(ex, st)
    t.SetElse(v)
    t = v
  }

  if p.look == scanner.Else {
    p.match(scanner.Else)
    se, _ := p.sequence()
    t.SetElse(se)
  }

  p.match(scanner.End)
  return res, nil
}

func (p *Parser) looping() (astexec.Statement, error) {
  p.match(scanner.While)
  ex, _ := p.relation()
  p.match(scanner.Do)
  st, _ := p.sequence()
  p.match(scanner.End)
  return astexec.NewLooping(ex, st), nil
}


